{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### ❇️ Tensors "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tensors are data structures similar to arrays and matrices wjich are use to encode inputs, outputs and parameters of models <br>\n",
    "Tensors are similar to NumPy’s ndarrays, except that tensors can run on GPUs or other hardware accelerators"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Initializing a Tensor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Directly from data\n",
    "data = [[1, 2],[3, 4]]\n",
    "x_data = torch.tensor(data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# From a NumPy array\n",
    "np_array = np.array(data)\n",
    "x_np = torch.from_numpy(np_array)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Random Tensor: \n",
      " tensor([[0.2710, 0.9181],\n",
      "        [0.0190, 0.6447]]) \n",
      "\n",
      "Ones Tensor: \n",
      " tensor([[1., 1.],\n",
      "        [1., 1.]]) \n",
      "\n",
      "Zeros Tensor: \n",
      " tensor([[0., 0.],\n",
      "        [0., 0.]])\n"
     ]
    }
   ],
   "source": [
    "# With random or constant values:\n",
    "shape=(2, 2)\n",
    "rand_tensor = torch.rand(shape)\n",
    "ones_tensor = torch.ones(shape)\n",
    "zeros_tensor = torch.zeros(shape)\n",
    "\n",
    "print(f\"Random Tensor: \\n {rand_tensor} \\n\")\n",
    "print(f\"Ones Tensor: \\n {ones_tensor} \\n\")\n",
    "print(f\"Zeros Tensor: \\n {zeros_tensor}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Moving tensors to GPU"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By default tensors are created on CPU We need to explicitly move tensors to the GPU using .to method"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# We move our tensor to the GPU if available\n",
    "tensor = torch.rand(2, 3)\n",
    "if torch.cuda.is_available():\n",
    "    tensor = tensor.to(\"cuda\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Attributes of a Tensor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape of tensor: torch.Size([2, 3])\n",
      "Datatype of tensor: torch.float32\n",
      "Tensor is stored on: cpu\n"
     ]
    }
   ],
   "source": [
    "tensor = torch.rand(2, 3)\n",
    "\n",
    "print(f\"Shape of tensor: {tensor.shape}\")\n",
    "print(f\"Datatype of tensor: {tensor.dtype}\")\n",
    "print(f\"Tensor is stored on: {tensor.device}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Operations on Tensors\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "First row: tensor([1., 1., 1., 1.])\n",
      "First column: tensor([1., 1., 1., 1.])\n",
      "Last column: tensor([1., 1., 1., 1.])\n",
      "tensor([[1., 0., 1., 1.],\n",
      "        [1., 0., 1., 1.],\n",
      "        [1., 0., 1., 1.],\n",
      "        [1., 0., 1., 1.]])\n"
     ]
    }
   ],
   "source": [
    "# Standard indexing and slicing just like NumPy\n",
    "tensor = torch.ones(4, 4)\n",
    "print(f\"First row: {tensor[0]}\")\n",
    "print(f\"First column: {tensor[:, 0]}\")\n",
    "print(f\"Last column: {tensor[..., -1]}\")\n",
    "tensor[:,1] = 0\n",
    "print(tensor)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Arithmetic Ops\n",
    "# This computes the matrix multiplication between two tensors.\n",
    "y1 = tensor.matmul(tensor.T)\n",
    "\n",
    "# This computes the element-wise product.\n",
    "z1 = tensor * tensor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "12.0 <class 'float'>\n"
     ]
    }
   ],
   "source": [
    "# Converting single value tensors to a Python \n",
    "# Numerical value\n",
    "agg = tensor.sum()\n",
    "agg_item = agg.item()\n",
    "print(agg_item, type(agg_item))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 0., 1., 1.],\n",
      "        [1., 0., 1., 1.],\n",
      "        [1., 0., 1., 1.],\n",
      "        [1., 0., 1., 1.]]) \n",
      "\n",
      "tensor([[6., 5., 6., 6.],\n",
      "        [6., 5., 6., 6.],\n",
      "        [6., 5., 6., 6.],\n",
      "        [6., 5., 6., 6.]])\n"
     ]
    }
   ],
   "source": [
    "# Inplace Ops\n",
    "print(f\"{tensor} \\n\")\n",
    "tensor.add_(5)\n",
    "print(tensor)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Bridge with NumPy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tensors on the CPU and NumPy arrays can share their underlying memory locations, and changing one will change the other.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor: tensor([1., 1., 1., 1., 1.])\n",
      "numpy array: [1. 1. 1. 1. 1.]\n"
     ]
    }
   ],
   "source": [
    "t = torch.ones(5)\n",
    "print(f\"tensor: {t}\")\n",
    "n = t.numpy()\n",
    "print(f\"numpy array: {n}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor: tensor([2., 2., 2., 2., 2.])\n",
      "numpy array: [2. 2. 2. 2. 2.]\n"
     ]
    }
   ],
   "source": [
    "t.add_(1)\n",
    "print(f\"tensor: {t}\")\n",
    "print(f\"numpy array: {n}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "env_torch",
   "language": "python",
   "name": "env_torch"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
